Jelajahi pola otentikasi yang kuat dan aman jenis menggunakan JWT di TypeScript, memastikan aplikasi global yang aman dan terpelihara. Pelajari praktik terbaik untuk mengelola data pengguna, peran, dan izin dengan peningkatan keamanan jenis.
Otentikasi TypeScript: Pola Keamanan Jenis JWT untuk Aplikasi Global
Di dunia yang saling terhubung saat ini, membangun aplikasi global yang aman dan andal adalah yang terpenting. Otentikasi, proses memverifikasi identitas pengguna, memainkan peran penting dalam melindungi data sensitif dan memastikan akses yang sah. JSON Web Tokens (JWT) telah menjadi pilihan populer untuk menerapkan otentikasi karena kesederhanaan dan portabilitasnya. Jika digabungkan dengan sistem tipe TypeScript yang canggih, otentikasi JWT dapat dibuat lebih kuat dan mudah dipelihara, terutama untuk proyek internasional skala besar.
Mengapa Menggunakan TypeScript untuk Otentikasi JWT?
TypeScript memberikan beberapa keuntungan saat membangun sistem otentikasi:
- Keamanan Jenis: Pengetikan statis TypeScript membantu mendeteksi kesalahan lebih awal dalam proses pengembangan, mengurangi risiko kejutan saat runtime. Ini sangat penting untuk komponen sensitif keamanan seperti otentikasi.
- Peningkatan Kemudahan Pemeliharaan Kode: Jenis memberikan kontrak dan dokumentasi yang jelas, sehingga lebih mudah untuk memahami, memodifikasi, dan memfaktorkan ulang kode, terutama dalam aplikasi global yang kompleks di mana beberapa pengembang mungkin terlibat.
- Penyempurnaan Penyelesaian Kode dan Alat: IDE yang mendukung TypeScript menawarkan penyelesaian kode, navigasi, dan alat refactoring yang lebih baik, meningkatkan produktivitas pengembang.
- Mengurangi Boilerplate: Fitur seperti antarmuka dan generik dapat membantu mengurangi kode boilerplate dan meningkatkan penggunaan kembali kode.
Memahami JWT
JWT adalah cara ringkas dan aman URL untuk mewakili klaim yang akan ditransfer antara dua pihak. Ini terdiri dari tiga bagian:
- Header: Menentukan algoritma dan jenis token.
- Payload: Berisi klaim, seperti ID pengguna, peran, dan waktu kedaluwarsa.
- Tanda Tangan: Memastikan integritas token menggunakan kunci rahasia.
JWT biasanya digunakan untuk otentikasi karena mereka dapat dengan mudah diverifikasi di sisi server tanpa perlu meminta database untuk setiap permintaan. Namun, menyimpan informasi sensitif secara langsung di payload JWT umumnya tidak disarankan.
Menerapkan Otentikasi JWT Aman Jenis di TypeScript
Mari kita jelajahi beberapa pola untuk membangun sistem otentikasi JWT aman jenis di TypeScript.
1. Mendefinisikan Jenis Payload dengan Antarmuka
Mulailah dengan mendefinisikan antarmuka yang mewakili struktur payload JWT Anda. Ini memastikan bahwa Anda memiliki keamanan jenis saat mengakses klaim di dalam token.
interface JwtPayload {
userId: string;
email: string;
roles: string[];
iat: number; // Dikeluarkan Pada (cap waktu)
exp: number; // Waktu Kedaluwarsa (cap waktu)
}
Antarmuka ini mendefinisikan bentuk payload JWT yang diharapkan. Kami telah menyertakan klaim JWT standar seperti `iat` (dikeluarkan pada) dan `exp` (waktu kedaluwarsa) yang sangat penting untuk mengelola validitas token. Anda dapat menambahkan klaim lain yang relevan dengan aplikasi Anda, seperti peran atau izin pengguna. Merupakan praktik yang baik untuk membatasi klaim hanya pada informasi yang diperlukan untuk meminimalkan ukuran token dan meningkatkan keamanan.
Contoh: Menangani Peran Pengguna dalam Platform E-niaga Global
Pertimbangkan platform e-niaga yang melayani pelanggan di seluruh dunia. Pengguna yang berbeda memiliki peran yang berbeda:
- Admin: Akses penuh untuk mengelola produk, pengguna, dan pesanan.
- Penjual: Dapat menambahkan dan mengelola produk mereka sendiri.
- Pelanggan: Dapat menelusuri dan membeli produk.
Array `roles` dalam `JwtPayload` dapat digunakan untuk mewakili peran-peran ini. Anda dapat memperluas properti `roles` ke struktur yang lebih kompleks, yang mewakili hak akses pengguna secara granular. Misalnya, Anda dapat memiliki daftar negara tempat pengguna diizinkan untuk beroperasi sebagai penjual, atau serangkaian toko yang memiliki akses admin pengguna.
2. Membuat Layanan JWT yang Diketik
Buat layanan yang menangani pembuatan dan verifikasi JWT. Layanan ini harus menggunakan antarmuka `JwtPayload` untuk memastikan keamanan jenis.
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // Simpan dengan aman!
class JwtService {
static sign(payload: Omit, expiresIn: string = '1h'): string {
const now = Math.floor(Date.now() / 1000);
const payloadWithTimestamps: JwtPayload = {
...payload,
iat: now,
exp: now + parseInt(expiresIn) * 60 * 60,
};
return jwt.sign(payloadWithTimestamps, JWT_SECRET);
}
static verify(token: string): JwtPayload | null {
try {
const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
return decoded;
} catch (error) {
console.error('Kesalahan verifikasi JWT:', error);
return null;
}
}
}
Layanan ini menyediakan dua metode:
- `sign()`: Membuat JWT dari payload. Diperlukan `Omit
` untuk memastikan bahwa `iat` dan `exp` dibuat secara otomatis. Penting untuk menyimpan `JWT_SECRET` dengan aman, idealnya menggunakan variabel lingkungan dan solusi manajemen rahasia. - `verify()`: Memverifikasi JWT dan mengembalikan payload yang didekode jika valid, atau `null` jika tidak valid. Kami menggunakan pernyataan jenis `as JwtPayload` setelah verifikasi, yang aman karena metode `jwt.verify` baik memunculkan kesalahan (ditangkap di blok `catch`) atau mengembalikan objek yang cocok dengan struktur payload yang kami definisikan.
Pertimbangan Keamanan Penting:
- Manajemen Kunci Rahasia: Jangan pernah mengkodekan kunci rahasia JWT Anda di kode Anda. Gunakan variabel lingkungan atau layanan manajemen rahasia khusus. Putar kunci secara teratur.
- Pilihan Algoritma: Pilih algoritma penandatanganan yang kuat, seperti HS256 atau RS256. Hindari algoritma yang lemah seperti `none`.
- Kedaluwarsa Token: Tetapkan waktu kedaluwarsa yang sesuai untuk JWT Anda untuk membatasi dampak token yang disusupi.
- Penyimpanan Token: Simpan JWT dengan aman di sisi klien. Opsi termasuk cookie HTTP-only atau penyimpanan lokal dengan tindakan pencegahan yang sesuai terhadap serangan XSS.
3. Melindungi Titik Akhir API dengan Middleware
Buat middleware untuk melindungi titik akhir API Anda dengan memverifikasi JWT di header `Authorization`.
import { Request, Response, NextFunction } from 'express';
interface RequestWithUser extends Request {
user?: JwtPayload;
}
function authenticate(req: RequestWithUser, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'Tidak Sah' });
}
const token = authHeader.split(' ')[1]; // Dengan asumsi token Bearer
const decoded = JwtService.verify(token);
if (!decoded) {
return res.status(401).json({ message: 'Token tidak valid' });
}
req.user = decoded;
next();
}
export default authenticate;
Middleware ini mengekstrak JWT dari header `Authorization`, memverifikasinya menggunakan `JwtService`, dan melampirkan payload yang didekode ke objek `req.user`. Kami juga mendefinisikan antarmuka `RequestWithUser` untuk memperluas antarmuka `Request` standar dari Express.js, menambahkan properti `user` bertipe `JwtPayload | undefined`. Ini memberikan keamanan jenis saat mengakses informasi pengguna dalam rute yang dilindungi.
Contoh: Menangani Zona Waktu dalam Aplikasi Global
Bayangkan aplikasi Anda memungkinkan pengguna dari zona waktu yang berbeda untuk menjadwalkan acara. Anda mungkin ingin menyimpan zona waktu pilihan pengguna dalam payload JWT untuk menampilkan waktu acara dengan benar. Anda dapat menambahkan klaim `timeZone` ke antarmuka `JwtPayload`:
interface JwtPayload {
userId: string;
email: string;
roles: string[];
timeZone: string; // misalnya, 'America/Los_Angeles', 'Asia/Tokyo'
iat: number;
exp: number;
}
Kemudian, dalam penangan rute middleware atau Anda, Anda dapat mengakses `req.user.timeZone` untuk memformat tanggal dan waktu sesuai dengan preferensi pengguna.
4. Menggunakan Pengguna yang Diautentikasi dalam Penangan Rute
Dalam penangan rute yang dilindungi, Anda sekarang dapat mengakses informasi pengguna yang diautentikasi melalui objek `req.user`, dengan keamanan jenis penuh.
import express, { Request, Response } from 'express';
import authenticate from './middleware/authenticate';
const app = express();
app.get('/profile', authenticate, (req: Request, res: Response) => {
const user = (req as any).user; // atau gunakan RequestWithUser
res.json({ message: `Halo, ${user.email}!`, userId: user.userId });
});
Contoh ini menunjukkan cara mengakses email dan ID pengguna yang diautentikasi dari objek `req.user`. Karena kami mendefinisikan antarmuka `JwtPayload`, TypeScript tahu struktur objek `user` yang diharapkan dan dapat menyediakan pemeriksaan jenis dan penyelesaian kode.
5. Menerapkan Kontrol Akses Berbasis Peran (RBAC)
Untuk kontrol akses yang lebih terperinci, Anda dapat menerapkan RBAC berdasarkan peran yang disimpan dalam payload JWT.
function authorize(roles: string[]) {
return (req: RequestWithUser, res: Response, next: NextFunction) => {
const user = req.user;
if (!user || !user.roles.some(role => roles.includes(role))) {
return res.status(403).json({ message: 'Dilarang' });
}
next();
};
}
Middleware `authorize` ini memeriksa apakah peran pengguna menyertakan peran yang diperlukan. Jika tidak, itu mengembalikan kesalahan 403 Forbidden.
app.get('/admin', authenticate, authorize(['admin']), (req: Request, res: Response) => {
res.json({ message: 'Selamat Datang, Admin!' });
});
Contoh ini melindungi rute `/admin`, yang mengharuskan pengguna memiliki peran `admin`.
Contoh: Menangani Mata Uang yang Berbeda dalam Aplikasi Global
Jika aplikasi Anda menangani transaksi keuangan, Anda mungkin perlu mendukung beberapa mata uang. Anda dapat menyimpan mata uang pilihan pengguna dalam payload JWT:
interface JwtPayload {
userId: string;
email: string;
roles: string[];
currency: string; // misalnya, 'USD', 'EUR', 'JPY'
iat: number;
exp: number;
}
Kemudian, dalam logika backend Anda, Anda dapat menggunakan `req.user.currency` untuk memformat harga dan melakukan konversi mata uang sesuai kebutuhan.
6. Token Refresh
JWT bersifat berumur pendek berdasarkan desain. Untuk menghindari pengguna harus sering masuk, terapkan token refresh. Token refresh adalah token berumur panjang yang dapat digunakan untuk mendapatkan token akses (JWT) baru tanpa mengharuskan pengguna memasukkan kembali kredensial mereka. Simpan token refresh dengan aman dalam database dan kaitkan dengan pengguna. Ketika token akses pengguna kedaluwarsa, mereka dapat menggunakan token refresh untuk meminta yang baru. Proses ini perlu diterapkan dengan hati-hati untuk menghindari kerentanan keamanan.
Teknik Keamanan Jenis Tingkat Lanjut
1. Persatuan Diskriminasi untuk Kontrol Halus
Terkadang, Anda mungkin memerlukan payload JWT yang berbeda berdasarkan peran pengguna atau jenis permintaan. Persatuan diskriminasi dapat membantu Anda mencapainya dengan keamanan jenis.
interface AdminJwtPayload {
type: 'admin';
userId: string;
email: string;
roles: string[];
iat: number;
exp: number;
}
interface UserJwtPayload {
type: 'user';
userId: string;
email: string;
iat: number;
exp: number;
}
type JwtPayload = AdminJwtPayload | UserJwtPayload;
function processToken(payload: JwtPayload) {
if (payload.type === 'admin') {
console.log('Email Admin:', payload.email); // Aman untuk mengakses email
} else {
// payload.email tidak dapat diakses di sini karena jenisnya 'user'
console.log('ID Pengguna:', payload.userId);
}
}
Contoh ini mendefinisikan dua jenis payload JWT yang berbeda, `AdminJwtPayload` dan `UserJwtPayload`, dan menggabungkannya menjadi gabungan yang dibedakan `JwtPayload`. Properti `type` bertindak sebagai pembeda, yang memungkinkan Anda untuk mengakses properti dengan aman berdasarkan jenis payload.
2. Generik untuk Logika Otentikasi yang Dapat Digunakan Kembali
Jika Anda memiliki beberapa skema otentikasi dengan struktur payload yang berbeda, Anda dapat menggunakan generik untuk membuat logika otentikasi yang dapat digunakan kembali.
interface BaseJwtPayload {
userId: string;
iat: number;
exp: number;
}
function verifyToken(token: string): T | null {
try {
const decoded = jwt.verify(token, JWT_SECRET) as T;
return decoded;
} catch (error) {
console.error('Kesalahan verifikasi JWT:', error);
return null;
}
}
const adminToken = verifyToken('admin-token');
if (adminToken) {
console.log('Email Admin:', adminToken.email);
}
Contoh ini mendefinisikan fungsi `verifyToken` yang mengambil jenis generik `T` yang memperluas `BaseJwtPayload`. Ini memungkinkan Anda untuk memverifikasi token dengan struktur payload yang berbeda sambil memastikan bahwa mereka semua memiliki setidaknya properti `userId`, `iat`, dan `exp`.
Pertimbangan Aplikasi Global
Saat membangun sistem otentikasi untuk aplikasi global, pertimbangkan hal berikut:
- Lokalisasi: Pastikan bahwa pesan kesalahan dan elemen antarmuka pengguna dilokalkan untuk berbagai bahasa dan wilayah.
- Zona Waktu: Tangani zona waktu dengan benar saat mengatur waktu kedaluwarsa token dan menampilkan tanggal dan waktu kepada pengguna.
- Privasi Data: Patuhi peraturan privasi data seperti GDPR dan CCPA. Minimalkan jumlah data pribadi yang disimpan dalam JWT.
- Aksesibilitas: Rancang alur otentikasi Anda agar dapat diakses oleh pengguna penyandang disabilitas.
- Sensitivitas Budaya: Perhatikan perbedaan budaya saat merancang antarmuka pengguna dan alur otentikasi.
Kesimpulan
Dengan memanfaatkan sistem tipe TypeScript, Anda dapat membangun sistem otentikasi JWT yang kuat dan mudah dipelihara untuk aplikasi global. Mendefinisikan jenis payload dengan antarmuka, membuat layanan JWT yang diketik, melindungi titik akhir API dengan middleware, dan menerapkan RBAC adalah langkah penting dalam memastikan keamanan dan keamanan jenis. Dengan mempertimbangkan pertimbangan aplikasi global seperti lokalisasi, zona waktu, privasi data, aksesibilitas, dan sensitivitas budaya, Anda dapat membuat pengalaman otentikasi yang inklusif dan ramah pengguna untuk audiens internasional yang beragam. Ingatlah untuk selalu memprioritaskan praktik terbaik keamanan saat menangani JWT, termasuk manajemen kunci yang aman, pemilihan algoritma, kedaluwarsa token, dan penyimpanan token. Rangkul kekuatan TypeScript untuk membangun sistem otentikasi yang aman, terukur, dan andal untuk aplikasi global Anda.